Skip to content

Per-note windows (Apple Notes-style) + multi-window hardening#11

Merged
resure merged 5 commits into
mainfrom
note-windows
Jul 5, 2026
Merged

Per-note windows (Apple Notes-style) + multi-window hardening#11
resure merged 5 commits into
mainfrom
note-windows

Conversation

@resure

@resure resure commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Opens any note in its own desktop window, Apple Notes-style, on top of the existing
per-workspace windows — plus a round of live URLs, editor polish, and a thorough
code-review/field-test hardening pass.

What's new

Per-note windows (desktop). ⌘↵ / ⌘-click a list row (or its ⋯ → "Open in New
Window") opens just that note in a small note-N window: both side panels closed, focus
in the editor, native title tracking the note. Reopening the same note focuses its
existing window; ⌘0 (Window ▸ Main Window) brings back the workspace's main window.
⌘-click / ⌘↵ also works on "Open Folder…" to open a picked folder in its own window.

Editor & preview.

  • Live URLs: bare https://… links render as real links in the editor and preview,
    typed URLs linkify on the trailing space, and open in the browser via ⌘-click (plain
    click in preview). Explicit schemes only — a stray Notes.md never becomes a link or
    gets rewritten (.md is a real TLD).
  • Markup-mode undo is isolated per note (⌘Z no longer walks into the previous note).
  • A floating Preview chip while read-only preview is on; a folder-scope chip above
    the list when a folder filters it with the rail closed; search auto-peek of the
    collapsed sidebar while typing.

iCloud. The Rust walks skip not-yet-downloaded (dataless) files' content instead of
blocking on download — they list by name/mtime with empty previews, and search/preview
recover once macOS materializes the file.

Hardening (xhigh /code-review + field testing)

15 verified findings fixed, plus a ⌘0 double-window bug caught in field testing:

  • Note-window sidecar isolation — a note window's in-memory active pointer no longer
    leaks to the shared sidecar, so it can't hijack the main window's next-launch restore.
  • ⌘0 listener leak (altitude fix) — moved the menu:main-window listener from the
    workspace-keyed Workspace (remounted per ⌃R switch → duplicate listeners → ⌘0 opened
    several workspaces at once) to App (one stable listener per window). Trace-verified.
  • ⌘W keeps a window open when its flush hit an unresolved conflict (was dropping edits).
  • Typed-URL InputRule bails on selection-replacement + trims trailing punctuation; folder
    probe no longer skipped for stale main-window assignments; cross-window rename re-keys
    note-window focus-if-open; middle-click in preview routes through the vetted opener; and
    more.
  • Tooling: ignore stray root .vite/ cache and .claude/worktrees (made eslint . hang).

Testing

npm test 910/910 · tsc clean · cargo test 22/22 · npm run lint 0 errors · Prettier
clean. The per-note-window flows, ⌘0, minimized-window focus, and live URLs were also
verified by hand in the desktop app.

🤖 Generated with Claude Code

resure and others added 5 commits July 5, 2026 13:30
…-style)

Desktop only. Entry points: row ⋯/context menu "Open in New Window", ⌘↵ on a
focused list row, and ⌘-click on a row. The note-N window opens smaller with
both side panels closed, restores its ASSIGNED note (never touching the shared
sidecar's last-active pointer), lands focus in the editor body, and titles
itself after the note (rename included). Opening the same note again focuses
its existing window (per-note focus-if-open, kept current via set_window_note);
workspace-level focus-if-open skips note windows.

⌘0 / Window ▸ Main Window (native accelerator) surfaces the full workspace view
for the FOCUSED window's workspace — focusing the existing main/ws-N window or
creating one — never dragging forward a main window parked on another workspace.

Also, since main + a note window now routinely show one folder: the note list
re-reads on window focus (throttled); right-click / ⌘-click on a row no longer
move selection or steal focus; and new windows open flash-free (no welcome-card
flash during bootstrap — delayed spinner instead; shell-assigned windows skip
the folder probe; the empty-editor placeholder waits for the initial load).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ting, iCloud, window polish

Editor correctness:
- Markup-mode ⌘Z could walk back into the PREVIOUS note (the one reused
  CodeMirror instance records replace() in its history, and addToHistory:false
  only remaps old events). A pristine CM state snapshotted at mount now
  templates a fresh per-note state on every swap (editor/markupHistory.ts;
  @codemirror/state+commands become direct deps — single deduped copies).
- Wiki tooltip/suggest popups no longer pop on note open (stuck top-left):
  gated through swappingRef — which now also starts TRUE at mount, since a
  fresh window never runs the swap path — plus a detached-anchor guard, and a
  caret at a link's LEFT edge no longer counts as inside it.
- WKWebView occasionally left a stale paint of the caret line (a "doubled"
  ghost line, few px offset): .editor-pane gets its own compositing layer.

Live URLs (fuzzy off everywhere — .md is a real TLD; a Notes.md mention must
never linkify or be rewritten on disk):
- md.linkify in the WYSIWYG editor (bare URLs normalize once to <url> on save),
  mirrored in the preview transform; preview link clicks route through
  openExternalUrl instead of navigating the surface.
- Typed URLs linkify on the trailing space via an InputRule that sets the
  editor's raw-link attr — typed links round-trip as the bare url.

iCloud Drive: the bulk walks skip dataless (SF_DATALESS) file content instead
of blocking on download-on-demand — a non-downloaded folder lists instantly
with empty previews; an explicit note open still materializes the file.

Windows & workspace UX:
- Note windows cascade centered on the opener's monitor (opener+28px glued
  them onto the main window — read as a doubled editor).
- "Open Folder…" honors ⌘ (switcher ⌘↵/⌘-click, orb menu ⌘-click): picks a
  folder into its OWN window via pickFolderForNewWindow (registry entry
  persisted before the window spawns).
- Read-only preview shows a floating chip (hover: "Exit preview"; click exits).
- A folder scope chip names the filter when the rail is closed (click opens
  the rail, ✕ back to All Notes without switching notes).
- Typing a search while the sidebar is collapsed auto-peeks the list without
  stealing focus from the box; clearing or committing tucks it away.

Docs: CLAUDE.md + README caught up (per-note windows, ⌘0, live URLs, chips).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…edUrls

Two items from the note-windows branch review:

- NotePreview's click handler now vets target.getAttribute('href') instead
  of target.href, which resolves relative paths and footnote # anchors
  against the document base into openable http(s) URLs — so a relative
  [x](foo) opened a junk tab instead of being swallowed as the doc comment
  promised. Test added.
- linkifyTypedUrls gets the unit test every other new module had: the
  regex boundaries (^/\s, explicit scheme only, whitespace terminator) and
  the from = end - url.length mark math (URL marked exactly, raw-link attr
  set, typed space outside the inclusive:false mark), plus the
  link/code/wiki_link skip paths — driven through the same stub-builder +
  inputRules harness as the wikiLinkExtension test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eak, linkify/iCloud edges

Second xhigh /code-review pass over the note-windows branch (15 verified findings)
plus field-test follow-ups.

Correctness:
- useNotes: a note window's in-memory `active` pointer no longer leaks to the shared
  sidecar — writeMetadataNow substitutes the live on-disk value at the write boundary,
  so pins/appearance/navigation/close can't hijack the main window's restore pointer.
  onCloseRequested keeps a window open when its flush hit an unresolved conflict
  (instead of destroying it and dropping the edit).
- ⌘0 (Window ▸ Main Window): move the menu:main-window listener from workspace-keyed
  Workspace (remounts on every ⌃R switch → duplicate listeners → ⌘0 opened several
  workspaces at once) to App (one stable listener per window). window_note_renamed
  re-keys note-window focus-if-open on cross-window rename/move; all focus paths
  unminimize before show/focus; openInNewWindow fails loudly when the workspace left
  the registry; windowNote clears on the first in-place switch.
- linkifyTypedUrls: bail when the typed whitespace replaces a selection (offsets would
  mis-mark and leave the selection undeleted); trim trailing punctuation from the URL.
- useNotesStorage: skipProbe excludes the main window (its assignment is stale after a
  webview reload) and pickFolderForNewWindow probes the folder before touching the
  registry.
- useCorpus: re-read an iCloud-dataless file once its list preview materializes
  (download leaves mtime unchanged, so the updatedAt-keyed refresh missed it).
- NotePreview: middle-click (auxclick) also routes through the vetted external opener.
- Search Enter with the sidebar collapsed returns to the editor, not a hidden list.

Tooling/docs:
- eslint.config.js + .prettierignore: ignore stray root .vite/ cache and .claude/
  worktrees (gitignored full-repo copies that made `eslint .` hang / report phantoms).
- CLAUDE.md/README caught up; completed backlog items removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From an external review of the note-windows branch:

- M3 (blocking): unify the "open in new window" chord. Hoist isOpenInNewWindowChord
  to shortcuts.ts and use it in TopBar (recents + Open Folder…) and the ⌃R switcher,
  which previously gated on metaKey alone — so Ctrl-click/Ctrl↵ now works off macOS
  (Windows/Linux), matching the note-list rows.
- M1: notify the shell when a note is trashed or deleted (window_note_removed), the
  delete-path mirror of window_note_renamed — so a note window left showing an orphan
  stops answering per-note focus-if-open for a dead id. Pure core unit-tested.
- M2: cite the SDK header + verified macOS version for the hand-coded SF_DATALESS.
- N1/N3: document the notifyShellNoteRenamed ordering and that writeMetadataNow's
  diskActiveRef fallback is a load-time snapshot (best-effort, not authoritative).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@resure resure merged commit 96b4e8e into main Jul 5, 2026
2 checks passed
@resure resure deleted the note-windows branch July 5, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant